from tools import *
import os
import datetime
import subprocess
import time
import random
import json

LOG = getlogger(__name__)


class Job(object):
    def __init__(self,
                 name,
                 timer,
                 runner,
                 callbacks=[],
                 error_callbacks=[],
                 priority=0):
        self.is_running = False
        self.name = name
        self.runner = runner
        self.timer = timer
        self.callbacks = callbacks
        self.error_callbacks = error_callbacks
        self.last_result = None
        self.should_run = True
        self.signature = None
        self.priority = priority

    def should_not_run(self):
        return self.timer.next_run == datetime.datetime.max or not self.should_run

    @property
    def info(self):
        jobinfo = dict({
            'name': self.name,
            'last_run': self.timer.last_run,
            'next_run': self.timer.next_run,
            'last_result': self.last_result,
            'duration': self.runner.last_duration
        })
        return jobinfo

    def __lt__(self, other):
        if self.timer.next_run == other.timer.next_run:
            if self.priority and other.priority:
                return self.priority > other.priority
        return self.timer.next_run < other.timer.next_run

    def run(self):
        self.timer.tick()
        result = None
        try:
            LOG.info('job: %s, running' % self.name)
            result = self.runner.wrapped_run()
        except Exception, ex:
            LOG.warn('job: %s, error: %s' % (self.name, ex))
            for cb in self.error_callbacks:
                cb(ex, self)
        finally:
            LOG.info('job: %s, result %s' % (self.name, result))
            self.last_result = result
            ret = None
            for cb in self.callbacks:
                if ret is None:
                    ret = result
                ret = cb(ret, self)


class Runner(object):
    def __init__(self, module, params=None):
        self.running = False
        self.last_duration = None
        self.module = module
        self.params = params

    def wrapped_run(self):
        try:
            self.running = True
            start = time.time()
            return self.run()
        finally:
            self.last_duration = time.time() - start
            self.running = False


class ModuleRunner(Runner):
    def run(self):
        # jobfunc = __import__(self.job.module).main
        return self.module(*self.params)


class ShellRunner(Runner):  # module = exefile
    def run(self):
        # exefile = os.path.join(DIRS['shell'] + self.module)
        args = [self.module] + self.params
        p = subprocess.Popen(args)
        result = p.wait()
        return result


class Timer(object):
    def tack(self):
        d = datetime.datetime.now()
        if d < self.next_run:
            td = self.next_run - d
            time.sleep(td.seconds + 24 * 3600 * td.days)


class RepeatTimer(Timer):
    def tick(self):
        self.tack()
        self.last_run = datetime.datetime.now()
        self.next_run = self.next_run_time()

    def next_run_time(self):
        if isinstance(self.interval, list) and len(self.interval) > 0:
            p = self.interval[0]
            self.interval = self.interval[1:]
            delta = datetime.timedelta(**{self.unit: p})
            return datetime.datetime.now() + delta
        if self.last_run is None:
            return datetime.datetime.now()
        try:
            delta = datetime.timedelta(**{self.unit: self.interval})
            return datetime.datetime.now() + delta
        except:
            return datetime.datetime.max

    def __init__(self, interval, unit):
        self.last_run = None
        self.interval = interval
        self.unit = unit
        self.next_run = self.next_run_time()


class OnceTimer(Timer):
    def tick(self):  # reschdule
        if self.next_run != datetime.datetime.max:
            self.tack()
            self.last_run = datetime.datetime.now()
            self.next_run = datetime.datetime.max

    def __init__(self, when):
        self.last_run = None
        if when.lower() == "once":
            self.next_run = datetime.datetime.now()
        elif when.lower() == "start":
            init_delay = datetime.timedelta(**{'seconds': random.random()})
            self.next_run = datetime.datetime.now() + init_delay
        else:
            try:
                self.next_run = datetime.datetime.strptime(when,
                                                           "%Y%m%d%H%M%S")
            except:
                self.next_run = datetime.datetime.max


class Scheduler(object):
    def stop(self):
        self.run_flag = False

    def run(self):
        for run_job in self.job_iter():
            run_job()

    def job_iter(self):
        while self.run_flag and len(self.jobs) > 0:
            self.jobs.sort()
            j = self.jobs[0]
            if j.should_not_run():
                LOG.warn("all jobs seem to be finished.... quit")
                break
            yield j.run

    def get_job_by_name(self, name):
        matched = [j for j in self.jobs if j.name == name]
        if len(matched) > 0:
            return matched[0]

    def add_job(self, newjob):
        self.jobs.append(newjob)

    def delete_job(self, name):
        self.jobs = [j for j in self.jobs if j.name != name]

    def __init__(self, jobs):
        self.run_flag = True
        self.jobs = jobs


def gennerate_job(name, value, signature=None):
    keyset = set(value.keys())
    m = set(['shell', 'python']).intersection(keyset)
    t = set(['at', 'repeat']).intersection(keyset)
    assert len(m) == 1 and len(t) == 1
    runner = None
    if m == set(['shell']):
        exefile = os.path.join(DIRS['shell'] + value['shell']['module'])
        #chmod?
        assert os.path.isfile(exefile)
        p = value['shell'].get('params', [])
        runner = ShellRunner(exefile, p)
    elif m == set(['python']):
        pymodule = __import__(value['python']['module']).main
        p = value['python'].get('params', [])
        runner = ModuleRunner(pymodule, p)
    timer = None
    if t == set(['at']):
        timer = OnceTimer(value['at'])
    elif t == set(['repeat']):
        i = value['repeat']['interval']
        u = value['repeat']['unit']
        assert u in ('seconds', 'minutes', 'hours', 'days', 'weeks')
        timer = RepeatTimer(i, u)
    cbs = [CALLBACKS[k] for k in value.get('callbacks', []) if k in CALLBACKS]
    err_cbs = [
        ERR_CALLBACKS[k] for k in value.get('error_callbacks', [])
        if k in ERR_CALLBACKS
    ]
    prio = value.get('priority', 0)
    assert runner and timer
    job = Job(name, timer, runner, cbs, err_cbs, prio)
    if signature:
        job.signature = signature
    return job


def validate_and_convert_jobs(dirs, callbacks, error_callbacks, config_file):
    result = []
    if not os.path.isfile(config_file):
        config_file = os.path.join(dirs['conf'], config_file)
    try:
        assert os.path.isfile(config_file)
        with open(config_file) as data_file:
            conf = json.load(data_file)
            conf = byteify(conf)
            for name, value in conf.items():
                result.append(gennerate_job(name, value))
    except Exception, ex:
        LOG.error('config error %s', str(ex))
    return result
